#include "filesys/filesys.h"
#include <debug.h>
#include <stdio.h>
#include <string.h>
#include "filesys/file.h"
#include "filesys/free-map.h"
#include "filesys/inode.h"
#include "filesys/directory.h"
#include "devices/disk.h"
#include "threads/thread.h"
#ifdef FILESYS
#include "filesys/cache.h"
#endif

/* The disk that contains the file system. */
struct disk *filesys_disk;

static void do_format (void);

/* Initializes the file system module.
	 If FORMAT is true, reformats the file system. */
void
filesys_init (bool format) 
{
	filesys_disk = disk_get (0, 1);
	if (filesys_disk == NULL)
		PANIC ("hd0:1 (hdb) not present, file system initialization failed");

	inode_init ();
	free_map_init ();

	if (format) 
		do_format ();

	free_map_open ();
}

/* Shuts down the file system module, writing any unwritten data
	 to disk. */
void
filesys_done (void) 
{
	free_map_close ();
	flush_cache_blocks();
}

/* Creates a file named NAME with the given INITIAL_SIZE.
	 Returns true if successful, false otherwise.
	 Fails if a file named NAME already exists,
	 or if internal memory allocation fails. */
bool
filesys_create (const char *name, off_t initial_size, bool is_dir) 
{
	/*
		 disk_sector_t inode_sector = 0;
		 struct dir *dir = dir_open_root ();
		 bool success = (dir != NULL
		 && free_map_allocate (1, &inode_sector)
		 && inode_create (inode_sector, initial_size)
		 && dir_add (dir, name, inode_sector));
		 if (!success && inode_sector != 0) 
		 free_map_release (inode_sector, 1);
		 dir_close (dir);
		 */
	char real_name[NAME_MAX+1]={'\0',};
	if (!get_real_from_full (name, real_name)){
		return false;
	}

	disk_sector_t inode_sector = 0;
	struct dir *dir = get_dir_from_full(name, true);
	if(dir == NULL){
		return false;
	}

	if( !free_map_allocate (1, &inode_sector) ){
		dir_close(dir);
		return false;
	}

	if( !(is_dir? dir_create(inode_sector, 0) : inode_create(inode_sector, initial_size)) ){
		free_map_release (inode_sector, 1);
		dir_close (dir);
		return false;
	}

	if( !dir_add (dir, real_name, inode_sector) ){
		inode_remove (inode_open (inode_sector));
		free_map_release (inode_sector, 1);
		dir_close (dir);
		return false;
	}

	dir_close(dir);
	return true;
}

/* Opens the file with the given NAME.
	 Returns the new file if successful or a null pointer
	 otherwise.
	 Fails if no file named NAME exists,
	 or if an internal memory allocation fails. */
struct file *
filesys_open (const char *name)
{
	struct dir *dir = NULL;
	struct inode *inode = NULL;
	char real_name[NAME_MAX+1]={'\0',};

	if( name == NULL || strlen(name) == 0){
		return NULL;
	}



	if( strcmp(name, ".") == 0){
		int cur_idx;
		int temp_idx;
		struct inode *cur_inode;

		cur_idx = thread_current()->current_dir_idx;
		cur_inode = inode_open(cur_idx);

		if(cur_idx == ROOT_DIR_SECTOR)
			return file_open( cur_inode );

		struct dir *parent_dir;

		temp_idx = get_parent_dir_idx(cur_inode);
		if( temp_idx != cur_idx){
			inode_close(cur_inode);
			cur_inode = inode_open(temp_idx);
		}

		parent_dir = dir_open( cur_inode );

		struct dir_entry e;
		size_t ofs;

		for (ofs = 0; inode_read_at (parent_dir->inode, &e, sizeof e, ofs) == sizeof e; ofs += sizeof e) 
			if (e.inode_sector == cur_idx && e.in_use)
			{
				dir_close(parent_dir);
				return filesys_open (e.name); 
			}
		dir_close(parent_dir);
		return NULL;
	}

	dir = get_dir_from_full(name, true);
	if(dir == NULL){
		return NULL;
	}

	////////////////////////////////////////
	// not sure about this case
	// file name is empty
	if( !get_real_from_full( name, real_name ) ){
		if( strlen( real_name ) !=0){
			// name of the file is longer than 14
			dir_close(dir);
			return NULL;
		}
		inode = inode_reopen( dir_get_inode(dir) );
		dir_close(dir);
		return file_open( inode );
	}

	// original!
	if (dir != NULL)
		dir_lookup (dir, real_name, &inode);
	dir_close (dir);

	return file_open (inode);
}

/* Deletes the file named NAME.
	 Returns true if successful, false on failure.
	 Fails if no file named NAME exists,
	 or if an internal memory allocation fails. */
bool
filesys_remove (const char *name) 
{
	if( name == NULL || strlen(name) == 0){
		return NULL;
	}

	char real_name[NAME_MAX+1]={'\0',};
	if( !get_real_from_full( name, real_name ) ){
		return false;
	}

	struct dir *dir = NULL;
	dir = get_dir_from_full(name, true);
	if(dir == NULL){
		return false;
	}

	// not in directory
	struct inode *temp;
	if (!dir_lookup (dir, real_name, &temp))
	{
		dir_close (dir);
		return false;
	}

	// check condition
	bool success = true;
	if(!get_is_dir(temp)) // file case, remove file from the link
		success = dir_remove (dir, real_name);
	else 
	{
		struct dir *subdir = dir_open (temp);
		// check directory is empty or not
		if (dir_is_empty (subdir))
		{
			dir_close(subdir);
			success = dir_remove (dir, real_name);
		}
		else{
			dir_close(subdir);
			success = false;
		}
	}

	dir_close (dir);

	return success;
}

/* Formats the file system. */
static void
do_format (void)
{
	printf ("Formatting file system...");
	free_map_create ();
	if (!dir_create (ROOT_DIR_SECTOR, 16))
		PANIC ("root directory creation failed");
	free_map_close ();
	printf ("done.\n");
}
